package com.ctsi.hsph.spi;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.*;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

/**
 * 平数SPI 认证工具类
 * 用于处理与事件中枢的认证和请求交互，包括SSO相关功能
 */
@Slf4j
@Component
public class SpiAuthUtil {

    @Value("${spi.auth.appId}")
    private String appId;  // 应用ID，固定值

    @Value("${spi.auth.appKey}")
    private String appKey;  // 合并后的应用密钥配置

    @Value("${spi.auth.appSecret}")
    private String appSecret;  // 合并后的应用密钥配置

    @Value("${spi.auth.baseUrl}")
    private String baseUrl;  // 合并后的基础 URL 配置

    @Value("${spi.auth.authUrl}")
    private String baseAuthUrl; // 统一登陆跳转使用

    @Value("${spi.auth.redirectUri}")
    private String redirectUri;

    @Value("${spi.auth.logoutCallbackUri}")
    private String logoutCallbackUri;

    private static final String AUTH_PATH = "/app-auth/login";  // 授权接口路径
    private static final String SSO_AUTH_PATH = "/sso/auth";  // 登录状态校验路径
    private static final String SSO_CHECK_TICKET_PATH = "/sso/checkTicket";  // 根据ticket获取token路径
    private static final String SSO_USERINFO_PATH = "/sso/userinfo";  // 根据token获取用户信息路径
    private static final String SSO_SIGNOUT_PATH = "/sso/signout";  // 单点注销路径
    private static final int TIMEOUT_MS = 15000;  // 请求超时时间，15秒

    private String tokenName = "Access-Token";
    private String tokenValue;
    private volatile long tokenTimeout;
    private volatile long lastTokenTime;

    /**
     * 获取 token 值（字符串形式）
     * 如果 token 过期或不存在，内部调用 getSaTokenInfo 方法获取新 token 并进行缓存
     */
    public synchronized String getTokenStr() {
        return this.getSaTokenInfo().getTokenValue();
    }

    /**
     * 获取 SaTokenInfo 对象
     * 负责获取最新token数据并赋值到当前对象的成员变量中
     * @return SaTokenInfo 对象
     */
    public synchronized SaTokenInfo getSaTokenInfo() {
        long currentTime = System.currentTimeMillis();
        // 检查本地缓存的 token 是否可用
        if (StrUtil.isNotEmpty(tokenValue) && (currentTime - lastTokenTime) < (tokenTimeout * 1000)) {
            SaTokenInfo saTokenInfo = new SaTokenInfo();
            saTokenInfo.setTokenName(tokenName);
            saTokenInfo.setTokenValue(tokenValue);
            saTokenInfo.setTokenTimeout(tokenTimeout);
            // 其他字段如需缓存可补充
            return saTokenInfo;
        }

        // 缓存不存在或已过期，请求新token
        long timestamp = System.currentTimeMillis();
        String str = String.format("appKey=%s&appSecret=%s&timestamp=%s", appKey, appSecret, timestamp);
        String sign = SecureUtil.md5(str).toLowerCase();

        Map<String, Object> jsonObject = new HashMap<>();
        jsonObject.put("appKey", appKey);
        jsonObject.put("sign", sign);
        jsonObject.put("timestamp", timestamp);

        String authUrl = buildFullUrl(AUTH_PATH);

        try {
            HttpRequest httpRequest = HttpUtil.createPost(authUrl)
                    .contentType(ContentType.FORM_URLENCODED.getValue())
                    .form(jsonObject)
                    .timeout(TIMEOUT_MS);

            try (HttpResponse response = httpRequest.execute()) {
                String result = response.body();
                log.info("获取 token 响应: {}", result);

                JSONObject jsonResult = JSONUtil.parseObj(result);
                int code = jsonResult.getInt("code");
                if (code == 200) {
                    JSONObject data = jsonResult.getJSONObject("data");
                    SaTokenInfo saTokenInfo = JSONUtil.toBean(data, SaTokenInfo.class);
                    // 缓存关键信息
                    this.tokenName = saTokenInfo.getTokenName();
                    this.tokenValue = saTokenInfo.getTokenValue();
                    this.tokenTimeout = saTokenInfo.getTokenTimeout() != null ? saTokenInfo.getTokenTimeout() : 3600L;
                    this.lastTokenTime = currentTime;
                    return saTokenInfo;
                } else {
                    log.error("获取 token 失败: {}", result);
                    throw new RuntimeException("获取 token 失败: " + jsonResult.getStr("message"));
                }
            }
        } catch (Exception e) {
            log.error("获取 token 异常: ", e);
            throw new RuntimeException("获取 token 异常: " + e.getMessage());
        }
    }

    /**
     * 发送 GET 请求
     *
     * @param path 接口路径，例如 /spi/user/page
     * @return 响应结果
     */
    public String getRequest(String path) {
        try {
            // 确保 token 有效
            String token = getTokenStr();
            // 构建完整 URL
            String fullUrl = buildFullUrl(path);
            // 构建请求
            HttpRequest httpRequest = HttpUtil.createGet(fullUrl)
                    .header(tokenName, token)
                    .timeout(TIMEOUT_MS);
            try (HttpResponse response = httpRequest.execute()) {
                String result = response.body();
                log.info("GET 请求响应: {}", result);
                return result;
            }
        } catch (Exception e) {
            log.error("GET 请求异常: ", e);
            throw new RuntimeException("GET 请求异常: " + e.getMessage());
        }
    }

    /**
     * 发送 POST 请求
     *
     * @param path     接口路径，例如 /spi/resource/addResource
     * @param postData 请求体数据 建议使用Map<String, Object> 或者 POJO 对象
     * @return 响应结果
     */
    public String postRequest(String path, Object postData) {
        try {
            // 确保 token 有效
            String token = getTokenStr();
            // 构建完整 URL
            String fullUrl = buildFullUrl(path);
            // 构建请求
            HttpRequest httpRequest = HttpUtil.createPost(fullUrl)
                    .header(tokenName, token)
                    .contentType(ContentType.JSON.getValue())
                    .body(JSONUtil.toJsonStr(postData))
                    .timeout(TIMEOUT_MS);
            try (HttpResponse response = httpRequest.execute()) {
                String result = response.body();
                log.info("POST 请求响应: {}", result);
                return result;
            }
        } catch (Exception e) {
            log.error("POST 请求异常: ", e);
            throw new RuntimeException("POST 请求异常: " + e.getMessage());
        }
    }

    /**
     * 发送 DELETE 请求
     */
    public String deleteRequest(String path, Object body) {
        try {
            String token = getTokenStr();
            String fullUrl = buildFullUrl(path);
            HttpRequest httpRequest = HttpUtil.createRequest(Method.DELETE, fullUrl)
                    .header(tokenName, token)
                    .contentType(ContentType.JSON.getValue())
                    .body(JSONUtil.toJsonStr(body))
                    .timeout(TIMEOUT_MS);
            try (HttpResponse response = httpRequest.execute()) {
                String result = response.body();
                log.info("DELETE 请求响应: {}", result);
                return result;
            }
        } catch (Exception e) {
            log.error("DELETE 请求异常: ", e);
            throw new RuntimeException("DELETE 请求异常: " + e.getMessage());
        }
    }



    /**
     * 6.1 登录状态校验 (SSO 认证中心重定向)
     *
     * @return 响应结果
     */
    public void checkSsoAuth(HttpServletResponse response) throws IOException {
        String authUrl = baseAuthUrl + "/sso/auth" +
                "?client_id=" + appId +
                "&redirect=" + redirectUri;

        response.setContentType("application/json;charset=UTF-8");
        response.setHeader("Access-Control-Allow-Origin", "*"); // 正确配置 CORS
        PrintWriter out = response.getWriter();
        out.write("{\"code\":302, \"message\":\"需要跳转\", \"url\":\"" + authUrl + "\"}");
        out.flush();
        out.close();
    }

    /**
     * 6.2 根据 ticket 获取 token
     *
     * @param ticket          令牌
     * @return 响应结果
     */
    public String checkTicket(String ticket) {
        try {
            // 确保 token 有效
            String token = getTokenStr();
            // 构建完整 URL，带上查询参数
            String pathWithParams = String.format("%s?client_id=%s&ticket=%s&ssoLogoutCall=%s",
                    SSO_CHECK_TICKET_PATH, appId, ticket, logoutCallbackUri);
            String fullUrl = buildFullUrl(pathWithParams);
            // 构建请求
            HttpRequest httpRequest = HttpUtil.createGet(fullUrl)
                    .header(tokenName, token)
                    .timeout(TIMEOUT_MS);
            try (HttpResponse response = httpRequest.execute()) {
                String result = response.body();
                log.info("根据 ticket 获取 token 响应: {}", result);
                return result;
            }
        } catch (Exception e) {
            log.error("根据 ticket 获取 token 异常: ", e);
            throw new RuntimeException("根据 ticket 获取 token 异常: " + e.getMessage());
        }
    }

    /**
     * 6.3 根据 token 获取用户信息
     * @param accessToken token
     * @return 响应结果
     */
    public String getUserInfo(String accessToken) {
        try {
            String token = getTokenStr();
            String pathWithParams = String.format("%s?accessToken=%s", SSO_USERINFO_PATH, accessToken);
            String fullUrl = buildFullUrl(pathWithParams);
            HttpRequest httpRequest = HttpUtil.createGet(fullUrl)
                    .header(tokenName, token)
                    .timeout(TIMEOUT_MS);
            try (HttpResponse response = httpRequest.execute()) {
                String result = response.body();
                log.info("获取用户信息响应: {}", result);
                return result;
            }
        } catch (Exception e) {
            log.error("获取用户信息异常: ", e);
            throw new RuntimeException("获取用户信息异常: " + e.getMessage());
        }
    }

    /**
     * 6.4 单点注销
     *
     * @param loginId 登录ID
     * @return 响应结果
     */
    public String signOut(String loginId) {
        try {
            // 确保 token 有效
            String token = getTokenStr();
            // 构建完整 URL，带上查询参数
            String pathWithParams = String.format("%s?loginId=%s", SSO_SIGNOUT_PATH, loginId);
            String fullUrl = buildFullUrl(pathWithParams);
            // 构建请求
            HttpRequest httpRequest = HttpUtil.createGet(fullUrl)
                    .header(tokenName, token)
                    .timeout(TIMEOUT_MS);
            try (HttpResponse response = httpRequest.execute()) {
                String result = response.body();
                log.info("单点注销响应: {}", result);
                return result;
            }
        } catch (Exception e) {
            log.error("单点注销异常: ", e);
            throw new RuntimeException("单点注销异常: " + e.getMessage());
        }
    }

    /**
     * 构建完整 URL
     *
     * @param path 接口路径
     * @return 完整 URL
     */
    private String buildFullUrl(String path) {
        if (StrUtil.isEmpty(path)) {
            throw new IllegalArgumentException("接口路径不能为空");
        }
        // 确保 baseUrl 不以 / 结尾，path 以 / 开头
        String cleanedBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
        String cleanedPath = path.startsWith("/") ? path : "/" + path;
        return cleanedBaseUrl + cleanedPath;
    }

    /**
     * 构建完整 认证URL
     *
     * @param path 接口路径
     * @return 完整 URL
     */
    private String buildFullAuthUrl(String path) {
        if (StrUtil.isEmpty(path)) {
            throw new IllegalArgumentException("接口路径不能为空");
        }
        // 确保 baseUrl 不以 / 结尾，path 以 / 开头
        String cleanedBaseUrl = baseAuthUrl.endsWith("/") ? baseAuthUrl.substring(0, baseAuthUrl.length() - 1) : baseAuthUrl;
        String cleanedPath = path.startsWith("/") ? path : "/" + path;
        return cleanedBaseUrl + cleanedPath;
    }


    /**
     * 将字符串转换为一个长整型数字。
     * 使用 SHA-256 哈希算法来生成一个唯一的数字键。
     *
     * @param input 输入字符串
     * @return 转换后的长整型数字
     */
    public static long convertToNumericKey(String input) {
        try {
            // 选择 SHA-256 哈希算法
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(input.getBytes());

            // 将哈希数组转换为一个长整型数字
            long numericKey = 0;
            for (int i = 0; i < 8; i++) { // 取前8个字节
                numericKey = (numericKey << 8) | (hash[i] & 0xFF);
            }
            log.info("转换字符串 '{}' 为数字键: {}", input, numericKey);
            return Math.abs(numericKey); // 确保是正数
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
}
